﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PodpisyElektorniczne.Classes;
using BIG = System.Numerics.BigInteger;
using System.Diagnostics;
using System.Security.Cryptography;
using System.IO;
using System.Xml.Serialization;


namespace SRSTests
{
    public class TestResults
    {
        private string path = "E:\\TestyMgr";
        private string file = "test_";
        public byte[] message { get; set; }
        public List<byte[]> encrypted { get; set; }
        public List<byte[]> decrypted { get; set; }
        public int keySize { get; set; }
        public long eTime { get; set; }
        public long dTime { get; set; }
        public int blockSize { get; set; }


        public TestResults() { }
        public static UnicodeEncoding ByteConverter = new UnicodeEncoding();
        public static ASCIIEncoding bConver = new ASCIIEncoding();
        public static UTF8Encoding con8 = new UTF8Encoding();
        public static UTF32Encoding con32 = new UTF32Encoding();
        public override string ToString()
        {
            string enc = string.Empty;
            string dec = string.Empty;

            foreach (byte[] i in encrypted)
                enc += con8.GetString(i);

            foreach (byte[] j in decrypted)
                dec += con8.GetString(j);

            string pref = string.Format("Key Size: {0} \n Bloc Size: {1}", this.keySize, this.blockSize);

            return string.Format("{5}\nMessage: {0} \n signature: {1} \n key size: {2} \n signing time: {3} [ms]\n verifying time: {4} [ms]",
                con8.GetString(message), enc, keySize, eTime, dTime, pref);
            // ByteConverter.GetString(encrypted), ByteConverter.GetString(decrypted), keySize, eTime, dTime);
        }

        private static Random random = new Random((int)DateTime.Now.Ticks);//thanks to McAden
        public static string RandomString(int size)
        {
            StringBuilder builder = new StringBuilder();
            char ch;
            for (int i = 0; i < size; i++)
            {
                ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
                builder.Append(ch);
            }
            
            return builder.ToString();
        }

        public bool Serialize(string encType)
        {
            try
            {             

                if (!Directory.Exists(this.path))
                    Directory.CreateDirectory(this.path);

                using (var writer = new StreamWriter(string.Format("{0}{1}{2}{3}{4}",path,"\\",file,encType+"_"+this.keySize+"_"+this.message.Length,".xml"), false, Encoding.UTF8))
                {
                    new XmlSerializer(typeof(string)).Serialize(writer, this.ToString());
                }
                return true;
            }
            catch (Exception ex)
            {
                throw new Exception("There is problem with serialization of a key. \n" + ex.Message);
            }
        }
    }
    
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void SkdlTest()
        {
            //var skdl1 = new SkdlFactory(new BIG(2357),1024);

            //var skdl2 = new SkdlFactory(new BIG(2357), new BIG(2357), new BIG());

            //var skdl3 = new SkdlFactory(new BIG(2357), new BIG(2357), new BIG(), 2357);
            //var rem = new BIG();
            //var t =
            //    BIG.Parse(
            //        "4030891771505074177460792256191500063078942730730869333781019442741748132088794181101510393747900751690851692802230158839581080029994421527178004652690700");
            //var res =FactorizationFactory.sqrt(t, out rem);
            //if(res*res+rem==t) Console.WriteLine(@"Success! {0}", rem.ToString());

            //var result = SchemaSetts.FindPrimeGroupOrders(48);
            //SchemaSetts.fac(109);

            //var i = (BIG[])SchemaSetts.FindPrimeGroupOrders(128);

            //Debug.WriteLine("p: {0}  q: {1}", i[0], i[1]);

            //var i =SchemaSetts.finder(64);

            //--------------RSA Test -----------------------------------------------------------------

            //Encryption block size: ((KeySize - 384) / 8) + 37, 394 - mini key size for RSA
            /***************test data ***********************/
            //the smallest key for RSA encoding is 384
            // ((t1.keySize - 384) / 8) + 37;

            var t2 = new TestResults
            {
                message = TestResults.con8.GetBytes(TestResults.RandomString(1300)),
                keySize = 512,
                encrypted = new List<byte[]>(),
                decrypted = new List<byte[]>()
            };
            t2.blockSize = 117;//((t2.keySize - 384) / 8) + 37;

            var t3 = new TestResults
            {
                message = TestResults.con8.GetBytes(TestResults.RandomString(1300)),
                keySize = 1024,
                encrypted = new List<byte[]>(),
                decrypted = new List<byte[]>()
            };
            t3.blockSize = 117;// ((t3.keySize - 384) / 8) + 37;

            var t4 = new TestResults
            {
                message = TestResults.con8.GetBytes(TestResults.RandomString(1300)),
                keySize = 2048,
                encrypted = new List<byte[]>(),
                decrypted = new List<byte[]>()
            };
            t4.blockSize = 117;// ((t4.keySize - 384) / 8) + 37;
            /************************************************/
            //rsaTest(t1);
            //rsaTest(t2);
            //rsaTest(t3);
            //rsaTest(t4);
            //t1.Serialize("RSA");
            //t2.Serialize("RSA");
            //t3.Serialize("RSA");
            //t4.Serialize("RSA");  
            long tt1 = 0;
            long tt2 = 0;
            for (int i = 0; i < 1000; i++)
            {
                var t1 = new TestResults
                {
                    message = TestResults.con8.GetBytes(TestResults.RandomString(13000)),
                    keySize = 1024,
                    encrypted = new List<byte[]>(),
                    decrypted = new List<byte[]>()
                };
                t1.blockSize = 117;
                dsaSignTest(t1);
                tt1 += t1.eTime;
                tt2 += t1.dTime;
            }

            Debug.WriteLine(string.Format("{0} {1}", tt1 / 1000.0, tt2 / 1000.0));
            //rsaSignTest(t2);
            //rsaSignTest(t3);
            //rsaSignTest(t4);

            //t1.Serialize("RSA");
            //Debug.WriteLine(t1.ToString());
            //t2.Serialize("RSA");
            //Debug.WriteLine(t2.ToString());
            //t3.Serialize("RSA");
            //Debug.WriteLine(t3.ToString());
            //t4.Serialize("RSA");
            //Debug.WriteLine(t4.ToString());

            //------------------------------------------------------------------------------------------

            //------------------ DSA -----

            //dsaSignTest(t1);
            //dsaSignTest(t2);
            //dsaSignTest(t3); 
            //dsaSignTest(t4);

            //t1.Serialize("DSA");
            //t2.Serialize("DSA");
            //t3.Serialize("DSA");
            //t4.Serialize("DSA");
            
            //---------------------------


            //RhoMethod rm = new RhoMethod();
            //RhoMethod.Factorize(BIG.Parse("224011332895528626675636622206940470793") - 1, 52);//BIG.Parse("224011332895528626675636622206940470793"));
            //Debug.WriteLine("Koniec");
        }

        public void dsaSignTest(TestResults ts)
        {
            try
            {
                //Create a new instance of DSACryptoServiceProvider to generate
                //a new key pair.
                DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();
                Stopwatch sw = new Stopwatch();
                //The data to sign.
                //byte[] Data = { 59, 4, 248, 102, 77, 97, 142, 201, 210, 12, 224, 93, 25, 41, 100, 197, 213, 134, 130, 135 };
                sw.Start();
                //The value to hold the signed value.
                byte[] SignedValue = DSASignData(ts.message, DSA.ExportParameters(true));
                sw.Stop();
                ts.eTime = sw.ElapsedMilliseconds;
                ts.encrypted.Add(SignedValue);
                sw.Reset();
                //Verify the data and display the results.
                sw.Start();
                if (DSAVerifyData(ts.message, SignedValue, DSA.ExportParameters(false)))
                {
                    Console.WriteLine("The hash value was verified.");
                }
                else
                {
                    Console.WriteLine("The hash value was not verified.");
                }
                sw.Stop();
                ts.dTime = sw.ElapsedMilliseconds;

            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine(e.Message);
            }
        }

        public static byte[] DSASignData(byte[] DataToSign, DSAParameters DSAKeyInfo)
        {
            try
            {
                //Create a new instance of DSACryptoServiceProvider.
                DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();

                //Import the key information.   
                DSA.ImportParameters(DSAKeyInfo);

                //Compute hash value, sign the hash, and return the signed hash.
                return DSA.SignData(DataToSign);
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return null;
            }

        }

        public static bool DSAVerifyData(byte[] Data, byte[] SignedData, DSAParameters DSAKeyInfo)
        {
            try
            {
                //Create a new instance of DSACryptoServiceProvider.
                DSACryptoServiceProvider DSA = new DSACryptoServiceProvider();

                //Import the key information. 
                DSA.ImportParameters(DSAKeyInfo);

                //Verify the signature and return the result.	
                return DSA.VerifyData(Data, SignedData);

            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return false;
            }

        }

        public void rsaSignTest(TestResults ts)
        {
            try
            {
                // Create a UnicodeEncoder to convert between byte array and string.
                
                Stopwatch sw = new Stopwatch();
                // Create byte arrays to hold original, encrypted, and decrypted data.
                byte[] originalData =ts.message;
                byte[] signedData;

                // Create a new instance of the RSACryptoServiceProvider class 
                // and automatically create a new key-pair.
                RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();

                // Export the key information to an RSAParameters object.
                // You must pass true to export the private key for signing.
                // However, you do not need to export the private key
                // for verification.
                RSAalg.KeySize = ts.keySize;
                RSAParameters Key = RSAalg.ExportParameters(true);

                // Hash and sign the data.
                sw.Start();
                ts.encrypted.Add( signedData = HashAndSignBytes(ts.keySize, originalData, Key));
                sw.Stop();
                ts.eTime = sw.ElapsedMilliseconds;
                sw.Reset();
                // Verify the data and display the result to the 
                // console.
                sw.Start();
                if (VerifySignedHash(ts.keySize,originalData, signedData, Key))
                {
                    Console.WriteLine("The data was verified.");
                }
                else
                {
                    Console.WriteLine("The data does not match the signature.");
                }
                sw.Stop();
                ts.dTime = sw.ElapsedMilliseconds;

            }
            catch (ArgumentNullException)
            {
                Console.WriteLine("The data was not signed or verified");

            }
        
        }


        public void rsaTest(TestResults ts)
        {
            Stopwatch sw = new Stopwatch();

            try
            {
                //Create a UnicodeEncoder to convert between byte array and string.
                //UnicodeEncoding ByteConverter = new UnicodeEncoding();
                //ASCIIEncoding bConver = new ASCIIEncoding();
                //UTF8Encoding con = new UTF8Encoding();
                //Create byte arrays to hold original, encrypted, and decrypted data.
                               
                byte[] encryptedData;
                byte[] decryptedData;
                
                
                //Create a new instance of RSACryptoServiceProvider to generate
                //public and private key data.
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {

                    //Pass the data to ENCRYPT, the public key information 
                    //(using RSACryptoServiceProvider.ExportParameters(false),
                    //and a boolean flag specifying no OAEP padding.
                    int c = ts.message.Length / ts.blockSize;
                    for (int i = 0; i < c; i++)
                    {
                        byte[] dataToEncrypt = new byte[ts.blockSize];
                        
                        for (int j = 0; j < ts.blockSize; j++) dataToEncrypt[j] = ts.message[i * ts.blockSize + j];
                        sw.Start();
                        encryptedData = RSAEncrypt(ts.keySize, dataToEncrypt, RSA.ExportParameters(false), false);
                        sw.Stop();
                        ts.encrypted.Add(encryptedData);
                        ts.eTime += sw.ElapsedMilliseconds;
                        //Debug.WriteLine(string.Format("Decrypted plaintext: {0}, time: {1}", con.GetString(encryptedData), sw.ElapsedMilliseconds));
                        sw.Reset();
                        sw.Start();
                        //Console.WriteLine("Decrypted plaintext: {0}", ByteConverter.GetString(decryptedData));
                        //Pass the data to DECRYPT, the private key information 
                        //(using RSACryptoServiceProvider.ExportParameters(true),
                        //and a boolean flag specifying no OAEP padding.
                        decryptedData = RSADecrypt(ts.keySize, encryptedData, RSA.ExportParameters(true), false);
                        sw.Stop();
                        ts.decrypted.Add(decryptedData);
                        ts.dTime += sw.ElapsedMilliseconds;
                        sw.Reset();

                        encryptedData = null;
                        decryptedData = null;
                    }

                    int res = ts.keySize % ts.blockSize;
                    if (res != 0)
                    {
                        byte[] dataToEncrypt = new byte[res];
                        int pos = 0;
                        for (int j = ts.message.Length - res; j < ts.message.Length; j++) { dataToEncrypt[pos] = ts.message[j]; pos++; }
                        encryptedData = RSAEncrypt(ts.keySize, dataToEncrypt, RSA.ExportParameters(false), false);
                        sw.Stop();
                        ts.encrypted.Add(encryptedData);
                        ts.eTime += sw.ElapsedMilliseconds;
                        //Debug.WriteLine(string.Format("Decrypted plaintext: {0}, time: {1}", con.GetString(encryptedData), sw.ElapsedMilliseconds));
                        sw.Reset();
                        sw.Start();
                        //Console.WriteLine("Decrypted plaintext: {0}", ByteConverter.GetString(decryptedData));
                        //Pass the data to DECRYPT, the private key information 
                        //(using RSACryptoServiceProvider.ExportParameters(true),
                        //and a boolean flag specifying no OAEP padding.
                        decryptedData = RSADecrypt(ts.keySize, encryptedData, RSA.ExportParameters(true), false);
                        sw.Stop();
                        ts.decrypted.Add(decryptedData);
                        ts.dTime += sw.ElapsedMilliseconds;
                        sw.Reset();
                    }
                    //Display the decrypted plaintext to the console.
                    //Debug.WriteLine(string.Format("Decrypted plaintext: {0}, time: {1}", con.GetString(decryptedData), sw.ElapsedMilliseconds));
                    //Console.WriteLine("Decrypted plaintext: {0}", ByteConverter.GetString(decryptedData));
                }
            }
            catch (ArgumentNullException)
            {
                //Catch this exception in case the encryption did
                //not succeed.
                Console.WriteLine("Encryption failed.");

            }

        }


        public static byte[] HashAndSignBytes(int kSize, byte[] DataToSign, RSAParameters Key)
        {
            try
            {
                // Create a new instance of RSACryptoServiceProvider using the 
                // key from RSAParameters.  
                RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
                RSAalg.KeySize = kSize;
                RSAalg.ImportParameters(Key);

                // Hash and sign the data. Pass a new instance of SHA1CryptoServiceProvider
                // to specify the use of SHA1 for hashing.
                return RSAalg.SignData(DataToSign, new SHA1CryptoServiceProvider());
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return null;
            }
        }

        public static bool VerifySignedHash(int kSize,byte[] DataToVerify, byte[] SignedData, RSAParameters Key)
        {
            try
            {
                // Create a new instance of RSACryptoServiceProvider using the 
                // key from RSAParameters.
                RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();
                RSAalg.KeySize = kSize;
                RSAalg.ImportParameters(Key);

                // Verify the data using the signature.  Pass a new instance of SHA1CryptoServiceProvider
                // to specify the use of SHA1 for hashing.
                return RSAalg.VerifyData(DataToVerify, new SHA1CryptoServiceProvider(), SignedData);

            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return false;
            }
        }


        static public byte[] RSAEncrypt(int kSize,byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] encryptedData;
                //Create a new instance of RSACryptoServiceProvider.
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {

                    //Import the RSA Key information. This only needs
                    //toinclude the public key information.
                    RSA.KeySize = kSize;
                    RSA.ImportParameters(RSAKeyInfo);

                    //Encrypt the passed byte array and specify OAEP padding.  
                    //OAEP padding is only available on Microsoft Windows XP or
                    //later.  
                    encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);

                }
                return encryptedData;
            }
            //Catch and display a CryptographicException  
            //to the console.
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);

                return null;
            }

        }

        static public byte[] RSADecrypt(int kSize,byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
        {
            try
            {
                byte[] decryptedData;
                //Create a new instance of RSACryptoServiceProvider.
                using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider())
                {
                    //Import the RSA Key information. This needs
                    //to include the private key information.
                    RSA.KeySize = kSize;
                    RSA.ImportParameters(RSAKeyInfo);
                    // Create a new instance of RSACryptoServiceProvider using the 
                    // key from RSAParameters.
                    //RSACryptoServiceProvider RSAalg = new RSACryptoServiceProvider();

                    //RSAalg.ImportParameters(Key);

                    // Verify the data using the signature.  Pass a new instance of SHA1CryptoServiceProvider
                    // to specify the use of SHA1 for hashing.
                    //return RSAalg.VerifyData(DataToVerify, new SHA1CryptoServiceProvider(), SignedData); 
                    //Decrypt the passed byte array and specify OAEP padding.  
                    //OAEP padding is only available on Microsoft Windows XP or
                    //later.  
                    decryptedData = RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
                }
                return decryptedData;
            }
            //Catch and display a CryptographicException  
            //to the console.
            catch (CryptographicException e)
            {
                Console.WriteLine(e.ToString());
                return null;
            }
        }

    }
}
